﻿using System;
using System.Collections.Generic;
using NLog;
using System.Threading;
using ScheduleTask.Tasks;
using System.Text;

namespace ScheduleTask
{
    public class TaskBootstrapper
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private List<ITask> _tasks;
        private readonly CancellationTokenSource _cts;
        private IServiceProvider _serviceProvider;

        public TaskBootstrapper(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            _cts = new CancellationTokenSource();
        }

        public void StartAllTask()
        {
            LoadAllTask();
            RunAllTask();
        }

        private void LoadAllTask()
        {
            Logger.Info("Start Load Task...");
            _tasks = new List<ITask>();
            _tasks.Add(_serviceProvider.TryGetService<PasswordMonitorTask>());
            _tasks.Add(_serviceProvider.TryGetService<PasswordMonitorTask2>());
            Logger.Info("Total Load {0} task", _tasks.Count);
        }

        #region Run Task
        private void RunAllTask()
        {
            Logger.Info("Start Run Task...");
            foreach (var task in _tasks)
            {
                var currentTask = task;

                if (currentTask.TaskType == TaskType.PerSecond)
                {
                    RunSecondTask(currentTask);
                }

                if (currentTask.TaskType == TaskType.PerDay)
                {
                    RunPerDayTask(currentTask);
                }

                if (currentTask.TaskType == TaskType.PerRegularMinute)
                {
                    RunRegularTimeTask(currentTask);
                }

                Logger.Info("Task {0} is running", currentTask.TaskName);
            }
        }

        private void RunPerDayTask(ITask currentTask)
        {
            //if (!currentTask.StartTimeSpan)
            //    return;
            //var startTimeSpan = currentTask.WorkingStartTime.Value - currentTask.WorkingStartTime.Value.Date;
            //var endTimeSpan = currentTask.WorkingEndTime.Value - currentTask.WorkingEndTime.Value.Date;
            ThreadPool.QueueUserWorkItem(o =>
            {
                while (true)
                {
                    if (_cts.Token.IsCancellationRequested)
                    {
                        Logger.Warn("{0} Receive Cancellation Request", currentTask.TaskName);
                        break;
                    }

                    var nowTimepan = DateTime.Now - DateTime.Now.Date;
                    if (currentTask.StartTimeSpan.TotalSeconds.ToString("f0") == nowTimepan.TotalSeconds.ToString("f0"))
                    {
                        //if (long.Parse(nowTimepan.TotalSeconds.ToString("f0")) % currentTask.RunPeriodInSecond == 0)
                        //{
                        ExceptionHandler(currentTask.RunTask);
                        Logger.Trace("{0} Running", currentTask.TaskName);
                        Thread.Sleep(5 * 1000);//Wait 5 seconds when thask was done.
                        //}
                    }
                    Thread.Sleep(1 * 1000);
                }
            });
        }

        private void RunSecondTask(ITask currentTask)
        {
            if (currentTask.RunPeriodInSecond <= 0)
            {
                throw new Exception($"Task: {currentTask.TaskName} is a PerMinute Task, The RunPeriodInSecond property can't be empty");
            }
            ThreadPool.QueueUserWorkItem(o =>
            {
                var i = 0;
                while (true)
                {
                    if (_cts.Token.IsCancellationRequested)
                    {
                        Logger.Warn("{0} Receive Cancellation Request", currentTask.TaskName);
                        break;
                    }

                    if (i++ % currentTask.RunPeriodInSecond == 0)
                    {
                        i = 1;
                        ExceptionHandler(currentTask.RunTask);
                        Logger.Trace("{0} Running", currentTask.TaskName);
                    }
                    Thread.Sleep(1 * 1000);
                }
            });
        }

        private void RunRegularTimeTask(ITask currentTask, bool isLimitWorkingTime = false)
        {
            if (currentTask.RunPeriodInSecond <= 0)
            {
                throw new Exception($"Task: {currentTask.TaskName} is a PerMinute Task, The RunPeriodInSecond property can't be empty");
            }

            TimeSpan startTimeSpan = TimeSpan.MinValue, endTimeSpan = TimeSpan.MinValue;
            if (isLimitWorkingTime)
            {
                startTimeSpan = currentTask.WorkingStartTime.Value - currentTask.WorkingStartTime.Value.Date;
                endTimeSpan = currentTask.WorkingEndTime.Value - currentTask.WorkingEndTime.Value.Date;
            }

            ThreadPool.QueueUserWorkItem(o =>
            {
                while (true)
                {
                    if (_cts.Token.IsCancellationRequested)
                    {
                        Logger.Warn("{0} Receive Cancellation Request", currentTask.TaskName);
                        break;
                    }
                    var nowTimepan = DateTime.Now - DateTime.Now.Date;//今天已过的时间
                    if (!isLimitWorkingTime || (isLimitWorkingTime && startTimeSpan <= nowTimepan && nowTimepan <= endTimeSpan))
                    {
                        if (long.Parse(nowTimepan.TotalSeconds.ToString("f0")) % currentTask.RunPeriodInSecond == 0)
                        {
                            ExceptionHandler(currentTask.RunTask);
                            Logger.Trace("{0} Running", currentTask.TaskName);

                        }
                    }
                    Thread.Sleep(1 * 1000);
                }
            });
        }

        private void ExceptionHandler(Action action, string handlerName = "")
        {
            try
            {
                action();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, $"{handlerName} task在执行时发生未指定的通用错误");
                //Logger.Error(ex, string.Format("{0} task在执行时发生未指定的通用错误, 错误信息:{1} FullStackTrace:{2}", handlerName, ex.FullMessage(), ex.FullStackTrace()));

            }
        }
        #endregion

        public void StopAllTask()
        {
            _cts.Cancel();
        }
    }

    public static class DependencyRegisterExtensions
    {
        public static object TryGetService(this IServiceProvider serviceProvider, Type type, bool throwExceptionIfReturnNull = true)
        {
            var constructors = type.GetConstructors();

            StringBuilder sbErrorMessage = new StringBuilder();

            foreach (var constructor in constructors)
            {
                try
                {
                    var parameters = constructor.GetParameters();
                    var parameterInstances = new List<object>();
                    foreach (var parameter in parameters)
                    {
                        var service = serviceProvider.GetService(parameter.ParameterType);
                        if (service == null) throw new Exception($"Can't reslove Type {parameter.ParameterType} when Create {type.FullName} object");
                        parameterInstances.Add(service);
                    }
                    return Activator.CreateInstance(type, parameterInstances.ToArray());
                }
                catch (Exception ex)
                {
                    sbErrorMessage.AppendLine(ex.Message);
                }
            }
            if (throwExceptionIfReturnNull)
            {
                throw new Exception(sbErrorMessage.ToString());
            }
            return null;
        }

        public static T TryGetService<T>(this IServiceProvider serviceProvider)
        {
            return (T)serviceProvider.TryGetService(typeof(T));
        }
    }
}
